// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: wsfuyibing <682805@qq.com>
// Date: 2024-12-11

package filter

import (
	"context"
	"gitee.com/go-libs/log"
	"gitee.com/gomq/sr/errs"
	"strings"
)

type (
	// Group is a logical and operational component used to store multiple
	// conditions
	Group interface {
		// GetError return the first error in the error list.
		GetError() error

		// GetErrors return the error list.
		GetErrors() []error

		// GetFields return condition field list.
		GetFields() []Field

		// HasError return true if error occurred when parse done.
		HasError() bool

		// Match matches the given value with expect value(s), Return true if
		// match success otherwise false returned.
		Match(ctx context.Context, data map[string]any) bool

		// Query returns a filter query expression like sql without field name.
		//
		//   - status = 1
		//   - status = 2 AND catalog IN (1,2,3)
		Query() string
	}

	group struct {
		Err        error
		ErrExists  bool
		ErrList    []error
		FieldCount int
		FieldList  []Field

		query string
	}
)

func NewGroup(str string) Group {
	o := &group{ErrList: make([]error, 0), FieldList: make([]Field, 0)}

	o.parse(str)
	o.ready()
	o.buildQuery()
	return o
}

// + Interface

func (o *group) GetError() error                                     { return o.Err }
func (o *group) GetErrors() []error                                  { return o.ErrList }
func (o *group) GetFields() []Field                                  { return o.FieldList }
func (o *group) HasError() bool                                      { return o.ErrExists }
func (o *group) Match(ctx context.Context, data map[string]any) bool { return o.match(ctx, data) }
func (o *group) Query() string                                       { return o.query }

// + Match

func (o *group) match(ctx context.Context, data map[string]any) (yes bool) {
	// Iterate fields in group. Return false if any field match failed.
	for _, v := range o.FieldList {
		if yes = v.Match(ctx, data); !yes {
			break
		}
	}

	log.Infofc(ctx, `[filter][group] match result: success="%v"`, yes)
	return
}

// + Access

func (o *group) buildQuery() {
	var (
		c = ""
		n = 0
	)
	for _, f := range o.FieldList {
		if f.HasError() {
			continue
		}
		o.query += c + f.Query()
		c = " AND "
		n++
	}
	if n > 1 {
		o.query = "(" + o.query + ")"
	}
}

func (o *group) parse(str string) {
	// Trim
	// space at start and end of expression.
	//
	//   str := `key == 1`
	//   str := `key == 1 AND value IN ("yes", "no")`
	if str = strings.TrimSpace(str); str == "" {
		return
	}

	// Remove brackets.
	//
	//   before: `(key == 1 AND value IN ("yes", "no"))`
	//   after : `key == 1 AND value IN ("yes", "no")`
	if regexMatchBracket.MatchString(str) {
		str = regexMatchBracket.ReplaceAllString(str, "$1")
		o.parse(str)
		return
	}

	// Iterate each fields.
	for _, s := range strings.Split(regexMatchLogicAnd.ReplaceAllString(str, "\n"), "\n") {
		if s = strings.TrimSpace(s); s != "" {
			f := NewField(s)
			if f.HasError() {
				o.ErrList = append(o.ErrList, f.GetError())
			} else {
				o.FieldList = append(o.FieldList, f)
			}
		}
	}
}

func (o *group) ready() {
	if len(o.ErrList) > 0 {
		o.Err = o.ErrList[0]
	} else {
		if o.FieldCount = len(o.FieldList); o.FieldCount == 0 {
			o.Err = errs.ErrFilterNoAnyCondition
			o.ErrList = append(o.ErrList, o.Err)
		}
	}
	o.ErrExists = o.Err != nil
}
